﻿using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Reflection;

using Tszy.Unlimited.Base.Collections;

namespace Tszy.Unlimited.Base.Extensions;

/// <summary>
/// 枚举器扩展
/// </summary>
public static class EnumerableExtensions
{
    /// <summary>
    /// 循环执行方法。
    /// </summary>
    /// <typeparam name="TSource">对象类型。</typeparam>
    /// <param name="enumerable">对象列表。</param>
    /// <param name="action">方法。</param>
    public static void ForEach<TSource>(this IEnumerable<TSource> enumerable, Action<TSource> action)
    {
        foreach (var item in enumerable)
        {
            action(item);
        }
    }

    /// <summary>
    /// 并行循环执行方法。
    /// </summary>
    /// <typeparam name="TSource">对象类型。</typeparam>
    /// <param name="enumerable">对象列表。</param>
    /// <param name="action">方法。</param>
    public static void ForEachParallel<TSource>(this IEnumerable<TSource> enumerable, Action<TSource> action) => Parallel.ForEach(enumerable, action);

    ///// <summary>
    ///// 机关函数应用False时：单个AND无效，多个AND无效；单个OR有效，多个OR有效；混应时写在OR后面的AND有效
    ///// </summary>
    ///// <typeparam name="T"></typeparam>
    ///// <returns></returns>
    //public static Expression<Func<T, bool>> False<T>(this IEnumerable<T> enumerable) => f => false;

    /// <summary>
    /// 将集合转换成只读集合。
    /// </summary>
    /// <typeparam name="T">对象类型。</typeparam>
    /// <param name="enumerable">对象列表。</param>
    /// <returns>对象列表。</returns>
    public static IList<T> ToReadOnlyCollection<T>(this IEnumerable<T> enumerable) => new ReadOnlyCollection<T>(enumerable.ToList());

    /// <summary>
    /// Or 条件拼接。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expr1"></param>
    /// <param name="expr2"></param>
    /// <returns></returns>
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);

        return Expression.Lambda<Func<T, bool>>(Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
    }

    /// <summary>
    /// And 条件拼接。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expr1"></param>
    /// <param name="expr2"></param>
    /// <returns></returns>
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);

        return Expression.Lambda<Func<T, bool>>(Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
    }

    /// <summary>
    /// 通过使用相等比较器对值进行比较返回序列中的非重复元素。
    /// </summary>
    /// <remarks>
    /// 示例1：var query = items.DistinctBy(p => p.Id);
    /// 示例2：var query = items.DistinctBy(p => new { p.Id, p.Name });
    /// </remarks>
    /// <typeparam name="TSource">集合类型。</typeparam>
    /// <typeparam name="TKey">比较元素的类型。</typeparam>
    /// <param name="source">集合。</param>
    /// <param name="keySelector">相等比较器（即：选择通过哪个元素来比较）。</param>
    /// <returns>非重复元素的集合。</returns>
    public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        var seenKeys = new HashSet<TKey>();

        return source.Where(element => seenKeys.Add(keySelector(element)));
    }

    /// <summary>
    /// 通过使用相等比较器对值进行比较生成两个序列的差集。
    /// </summary>
    /// <remarks>
    /// 即：返回所有属于 source 集合且不属于 target 集合的元素构成的集合。
    /// 示例1：var query = items1.ExceptBy(items2, item => item.Id);
    /// 示例2：var query = items.ExceptBy(items2, p => new { p.Id, p.Name });
    /// </remarks>
    /// <typeparam name="TSource">集合类型。</typeparam>
    /// <typeparam name="TKey">比较元素的类型。</typeparam>
    /// <param name="source">源集合。</param>
    /// <param name="target">过虑集合。</param>
    /// <param name="keySelector">相等比较器（即：选择通过哪个元素来比较）。</param>
    /// <returns>source 集合与 target 集合的差集。</returns>
    public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> source, IEnumerable<TSource> target, Func<TSource, TKey> keySelector)
    {
        var seenTargets = new HashSet<TKey>();

        target.ForEach(item => seenTargets.Add(keySelector(item)));

        var result = source.Where(element =>
        {
            var added = seenTargets.Add(keySelector(element));

            if (added)
            {
                seenTargets.Remove(keySelector(element));
            }

            return added;
        });

        return result;
    }

    #region 当前类型中存在的指定方法

    /// <summary>
    /// 当前类型中存在的指定方法。
    /// </summary>
    /// <param name="type"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    public static MethodInfo? GetOnlyDeclaredMethod(this Type type, string name)
    {
        return type.GetDeclaredMethods(name)?.SingleOrDefault();
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="type"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    public static IEnumerable<MethodInfo> GetDeclaredMethods(this Type type, string name)
    {
#if NET40
        const BindingFlags bindingFlags
            = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
        return type.GetMember(name, MemberTypes.Method, bindingFlags).OfType<MethodInfo>();
#else
        return type.GetTypeInfo().GetDeclaredMethods(name);
#endif
    }

    #endregion 当前类型中存在的指定方法

    #region 分页集合

    /// <summary>
    /// 将集合转换成分页集合。
    /// </summary>
    /// <typeparam name="TSource">类型。</typeparam>
    /// <param name="source">源集合。</param>
    /// <param name="pageIndex">页码。</param>
    /// <param name="pageSize">页大小。</param>
    /// <returns>分页集合。</returns>
    public static IPageOfItems<TSource> ToPage<TSource>(this IEnumerable<TSource> source, int pageIndex, int pageSize)
    {
        return new PageOfItems<TSource>(source, pageIndex, pageSize, 0, false);
    }

    /// <summary>
    /// 将集合转换成分页集合。
    /// </summary>
    /// <typeparam name="TSource">类型。</typeparam>
    /// <param name="source">源集合。</param>
    /// <param name="pageIndex">页码。</param>
    /// <param name="pageSize">页大小。</param>
    /// <param name="totalItemCount">总记录数。</param>
    /// <returns>分页集合。</returns>
    public static IPageOfItems<TSource> ToPage<TSource>(this IEnumerable<TSource> source, int pageIndex, int pageSize, int totalItemCount)
    {
        return new PageOfItems<TSource>(source, pageIndex, pageSize, totalItemCount);
    }

    /// <summary>
    /// 将集合转换成分页集合。
    /// </summary>
    /// <typeparam name="TSource">源类型。</typeparam>
    /// <typeparam name="TTarget">目标类型。</typeparam>
    /// <param name="source">源集合。</param>
    /// <param name="pageIndex">页码。</param>
    /// <param name="pageSize">页大小。</param>
    /// <param name="action">对象转换方法。</param>
    /// <returns>分页集合。</returns>
    public static IPageOfItems<TTarget> ToPage<TSource, TTarget>(this IEnumerable<TSource> source, int pageIndex, int pageSize, Func<TSource, TTarget> action)
    {
        return new PageOfItems<TTarget>(source.Select(action), pageIndex, pageSize, 0, false);
    }

    /// <summary>
    /// 将集合转换成分页集合。
    /// </summary>
    /// <typeparam name="TSource">源类型。</typeparam>
    /// <typeparam name="TTarget">目标类型。</typeparam>
    /// <param name="source">源集合。</param>
    /// <param name="pageIndex">页码。</param>
    /// <param name="pageSize">页大小。</param>
    /// <param name="totalItemCount">总记录数。</param>
    /// <param name="action">对象转换方法。</param>
    /// <returns>分页集合。</returns>
    public static IPageOfItems<TTarget> ToPage<TSource, TTarget>(this IEnumerable<TSource> source, int pageIndex, int pageSize, int totalItemCount, Func<TSource, TTarget> action)
    {
        return new PageOfItems<TTarget>(source.Select(action), pageIndex, pageSize, totalItemCount);
    }

    #endregion 分页集合
}

#region 匿名对象集合转换

internal abstract class EnumerableAnonymousObject
{
    public abstract object ToList(IEnumerable<object> items);
}

/// <summary>
/// 匿名对象集合转换。
/// </summary>
/// <typeparam name="TElementType"></typeparam>
internal class EnumerableAnonymousObject<TElementType> : EnumerableAnonymousObject
{
    public override object ToList(IEnumerable<object> items)
    {
        return items.OfType<TElementType>();
    }
}

#endregion 匿名对象集合转换
